from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from starlette.middleware.cors import CORSMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse, HTMLResponse, Response, StreamingResponse
from starlette.staticfiles import StaticFiles

import settings
from errors import JPGError, FileMaxSizeError

app = FastAPI(title='爱读书App接口服务',
              version='1.0',
              description='面向app，提交数据的API接口，包含会员、搜索、分类、活动、积分、评论、我的书架、广告等模块')

# 装载静态文件目录，并提供url访问的path “/s”
app.mount('/s', StaticFiles(directory=settings.STATIC_DIR))

# 添加跨域请求中间件
app.add_middleware(CORSMiddleware,
                   allow_origins='*',  # ACCESS_CONTROL_ALLOW_ORIGINS
                   allow_credentials=True,  # ACCESS_CONTROL_ALLOW_CREDENTIALS
                   allow_methods=["*"],  # ACCESS_CONTROL_ALLOW_METHODS
                   allow_headers=["*"],  # ACCESS_CONTROL_ALLOW_HEADERS
                  )

# 不用验证权限的path接口
pass_paths = [
    '/docs',
    '/openapi.json',
    '/login',
    '/regist',
]

# 定义中间件（实现日志收集）
@app.middleware('http')
async def logger_handler(request: Request, next_call):
    # 请求处理之前 提取请求路径中path
    # 如http://localhost:8000 /login, 提取是 /login
    path = str(request.url)[len(str(request.base_url))-1:]  # request的url和base_url -> URL类实例
    # 清除path路径的查询参数， /login?username=&password=
    path = path[:path.index('?')] if '?' in path else path

    # 放开请求路径，即不拦截
    if path in pass_paths:
        resp = await next_call(request)
        return resp

    headers = request.headers # 从请求头中验证是否存在token
    # 尝试获取json数据， 建议不使用数据
    try:
        json = await request.json()  # 上传的json数据,可能是不存在的
        print('-->request', json)
    except:
        pass
    # query:  QueryParams
    # 尝试获取查询参数和路径参数
    args = {'path_args': request.path_params, 'query_args': request.query_params}
    print('--->request:', path, headers, args, request.client.host)

    resp: StreamingResponse = await next_call(request)
    # 如果成功响应时，则记录本次的操作
    print('--->response: ', resp.status_code,resp.charset)
    return resp

# 自定义异常处理
@app.exception_handler(Exception)
async def handler_custom_error(request: Request, error):
    if isinstance(error, JPGError):
        return JSONResponse({'msg': '图片只支持jpg格式'}, 400)
    elif isinstance(error, FileMaxSizeError):
        resp = JSONResponse({'msg': '图片大小不能超过2M'}, 401, headers={'x-error': 'size out of 2M'})
        return resp


# 自定义处理请求数据验证的错误
@app.exception_handler(RequestValidationError)
async def handler_validation_error(req, e: RequestValidationError):
    print([a for a in dir(e) if not a.startswith('__')])
    errors = {
        'value_error.missing': '必填项',
        'type_error.integer': '字段内容必须是数值',
        'value_error.str.regex': '字段的数据不合法: {}',
        'value_error.number.not_ge': '数值太低: {}',
        'value_error.number.not_gt': '数值太低: {}',
        'value_error.number.not_le': '数值太高: {}',
        'value_error.number.not_lt': '数值太高: {}',
        'value_error.any_str.min_length': '字符串长度不足: {}',
        'value_error.any_str.max_length': '字符串超出最大长度: {}',
    }

    resp_error = {
        '.'.join(item['loc']): errors[item['type']].format(list(item['ctx'].values())[0]) if 'ctx' in item else errors[item['type']]
        for item in e.errors()
    }
    return JSONResponse(jsonable_encoder({'errors': resp_error}), status_code=400)

@app.exception_handler(404)
async def handler_not_found(request: Request, error):
    html = f"""
        <h1 style="color:red">请求的资源不存在，需要开发则联系管理员(Disen： 17791692095)</h1>
        <hr>
        <h3>当前请求的地址： { request.url }</h3>
    """
    return HTMLResponse(html, status_code=404)

@app.exception_handler(500)
async def handler_server_error(request: Request, error):
    html = f"""
        <h1 style="color:blue">出bug了，请联系管理员(Disen： 17791692095)</h1>
        <hr>
        <h3 style="color:red">{error}</h3>
    """
    return HTMLResponse(html, status_code=404)
